/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1/commercial
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may use this file in compliance with the License.
 * Alternatively, if you can't copy with the License, namely if you do not
 * want to provide source code of your modifications to this file, you may
 * negotiate a commercial licence with the initial developer.
 * You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Brano Kemen
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#ifndef __COID_SERVER_TREEMGR__HEADER_FILE__
#define __COID_SERVER_TREEMGR__HEADER_FILE__

#include "coid/comm/sync/mutex.h"
#include "serverface.h"
#include "service.h"
#include "serviceinst.h"
#include "tree/ttree.h"

namespace coid {

////////////////////////////////////////////////////////////////////////////////
template<class T>
struct NODEREF
{
    CoidNode* _node;
    int _detach;


    T* ptr()                            { return (T*)_node; }

    T* operator -> ()                   { return (T*)_node; }
    const T* operator -> () const       { return (const T*)_node; }

    operator T* ()                      { return (T*)_node; }
    operator const T* () const          { return (const T*)_node; }

    NODEREF( bool detach = 0 ) : _node(0),_detach(detach)  {}
    NODEREF( T* n, bool detach=false )  { set( n, detach ); }
    NODEREF( T& n, bool detach=false )  { set( &n, detach ); }

    NODEREF( NODEREF<T>& n )            { set( n._node, false ); }

    NODEREF& operator = ( NODEREF<T>& n )   { set( (T*)n._node, false );  return *this; }

    NODEREF& operator = ( T* n )        { set( n, false );  return *this; }
    NODEREF& operator = ( T& n )        { set( &n, false );  return *this; }

    
    bool is_set() const                 { return _node != 0; }
    void set_atexit_detach()            { _detach = 1; }
    void set_atexit_nothing()           { _detach = 0; }

    const charstr& name() const         { return _node->_name; }
    token class_name() const            { return _node->class_name(); }
    const version& get_version() const  { return _node->get_version(); }

    comm_mutex_reg* get_mutex() const   { return &_node->_mutex; }


    void set( T* n, bool detach )
    {
        _detach = detach;
        _node = n;
        *n->_thr_using.add() = (uint) thread::self();
    }

    void unset( bool exec=false )
    {
        if(_node)
        {
            _node->_thr_using.del_key( (uint)thread::self() );
            if( exec && _detach )
                _node->request_detach();
            _node = 0;
        }
    }
    
    ~NODEREF()
    {
        unset(true);
    }
};



////////////////////////////////////////////////////////////////////////////////
class TreeMgr
{
    typedef ttree<CoidNode,ttree_ifc>   t_tree;

    mutable comm_mutex  _mutex;
    t_tree  _tree;

    uint            _change_base;
    dynarray<uint>  _change_list;

    dynarray<LNID>  _deadlist;

    typedef t_tree::ID                  REF;

    ClassRegister<ServiceCoid>  _svcreg;
    ClassRegister<ServiceInstanceCoid> _svcireg;

    typedef ClassRegister<ServiceCoid>::NodeClass           ServiceClass;
    typedef ClassRegister<ServiceInstanceCoid>::NodeClass   ServiceInstanceClass;


public:
	TreeMgr()
    {
        _change_base = 0;
        _mutex.set_name( "TreeMgr" );
    }


    typedef t_tree::ObjList             ObjList;

    template<class T>
    opcd object_attach( T* node, NODEREF<T>& res, LNID par, LNID hookto=UMAX32 )
    {
        GUARDME;

        REF parid;
        if( par != UMAX32  &&  !_tree.get_node( par, parid ) )
            return ersUNAVAILABLE;
        if( par != UMAX32  &&  parid->_node->is_exit_signalled() )
            return ersDENIED;

        REF id;
        if( par == UMAX32 )
            id = _tree.attach_root( node, node->GET_NODECLASS() );
        else
            id = _tree.attach( par, node, node->GET_NODECLASS() );
        node->_id = (LNID)id;
		node->_mutex.set_objid( node->_id );

        if( hookto != UMAX32 )
        {
            REF hook;
            if( _tree.get_node( hookto, hook ) )
                _tree.hook( id, hook );
        }

        if( T::GET_INTERFACE().is_of_type( ServiceCoid::GET_INTERFACE() ) )
            _svcreg.add( (ServiceCoid*)node );

        if( T::GET_INTERFACE().is_of_type( ServiceInstanceCoid::GET_INTERFACE() )
            &&  (((ServiceInstanceCoid*)node)->_flags & ServiceInstanceCoid::fAUTOINSTANCE) ) 
            _svcireg.add( (ServiceInstanceCoid*)node );

        *_change_list.add() = tree_info::CHANGE_SET_OBJECT | (LNID)id;

        res.set( (T*)id.ptr(), true );
        return 0;
    }

    opcd request_detach( LNID obj, bool children_only );
    uints detach_dead();

    ///Detach object from list
    //opcd object_detach( LNID obj, dynarray<t_tree::ObjList>& dl, bool children_only );

    opcd object_hook( LNID obj, LNID to )
    {
        REF refo, refto;

        GUARDME;
        if( !_tree.get_node(obj, refo)  ||  !_tree.get_node(to, refto) )
            return ersINVALID_PARAMS;

        return  _tree.hook( refo, refto ) ? opcd(0) : ersFAILED;
    }

    template<class T>
    opcd get_referred( uint n, NODEREF<T>& res )
    {
        GUARDME;

        REF id;
        if( !_tree.get_node( n, id ) )
            return ersOUT_OF_RANGE "invalid index";

        dynarray<COID> rf;
        id->get_referred(rf);

        REF rid;
        for( uint i=0; i<rf.size(); ++i )
        {
            if( !_tree.get_node( rf[i], rid ) ) continue;
            if( !rid->_class.is_of_type( T::GET_INTERFACE() ) ) continue;
            if( !rid->_node->can_attach() )  continue;

            res = (T&)*rid;
            return 0;
        }

        return ersNOT_FOUND;
    }

    opcd get_detach_list( LNID n, dynarray<uint>& dl )
    {
        GUARDME;

        REF id;
        if( !_tree.get_node( n, id ) )
            return ersOUT_OF_RANGE "invalid index";

        dynarray<t_tree::ObjList> dlx;
        if( !_tree.get_detach_list( id, dlx ) )
            return ersINVALID_PARAMS;

        dl.need_new( dlx.size() );
        for( uint i=0; i<dlx.size(); ++i )
            dl[i] = (LNID) dlx[i].id;

        return 0;
    }

    opcd spawn_thread( LNID n, t_thread_fnc start, void* arg, bool crit, const token& name );

    opcd get_thread_list( LNID n, dynarray<thread>& thr_own, dynarray<thread>& thr_using )
    {
        GUARDME;

        REF id;
        if( !_tree.get_node( n, id ) )
            return ersOUT_OF_RANGE "invalid index";

        thr_own = id->_node->_thr_own;
        thr_using = id->_node->_thr_using;
        return 0;
    }


    template<class T>
    opcd get_node( LNID key, NODEREF<T>& node ) const
    {
        GUARDME;
        REF r;
        if( _tree.get_node( key, r )  &&  r->_class.is_of_type( T::GET_INTERFACE() ) )
        {
            if( !r->_node->can_attach() )  return ersDENIED;
            node = (T&)*r;
            return 0;
        }
        return ersINVALID_PARAMS;
    }

/*
    template<class T>
    opcd get_node_of_class( LNID key, const NodeClass nc, NODEREF<T>& node ) const
    {
        REF id;
        GUARDME;

        if( !_tree.get_node(key, id) )  return ersNOT_FOUND;
        if( !id->_class.is_of_type(nc) )  return ersMISMATCHED;
        node.set( id->_node, _mutex );
        return 0;
    }
*/
    template<class T>
    opcd get_node_of_class( LNID key, const token& name, const version* ver, NODEREF<T>& node ) const
    {
        REF id;
        GUARDME;

        if( !_tree.get_node( key, id ) )  return ersOUT_OF_RANGE "no such id";
        if( !id->_class.is_of_type(T::GET_INTERFACE(),ver) )  return ersMISMATCHED "node class";
		if( !name.is_empty()  &&  id->_node->_name != name )  return ersMISMATCHED "name";
        if( !id->_node->can_attach() )  return ersDENIED;
        node = (T*)id->_node;
        return 0;
    }

    struct Attribs
    {
        charstr name;
        NodeClass nc;
        uint flags;
    };

    opcd get_node_attribs( LNID n, Attribs& att ) const
    {
        REF id;
        GUARDME;

        if( !_tree.get_node(n, id) )  return ersNOT_FOUND;
        att.nc = id->_class;
        att.name = id->_node->_name;
        att.flags = id->_node->_flags;
        return 0;
    }

    opcd get_node_class( LNID n, NodeClass& nc ) const
    {
        REF id;
        GUARDME;

        if( !_tree.get_node(n, id) )  return ersNOT_FOUND;
        nc = id->_class;
        return 0;
    }

    opcd get_node_name( LNID n, token& name ) const
    {
        REF id;
        GUARDME;

        if( !_tree.get_node(n, id) )  return ersNOT_FOUND;
        name = id->_node->_name;
        return 0;
    }

    opcd set_node_name( LNID n, const token& name )
    {
        REF id;
        GUARDME;

        if( !_tree.get_node(n, id) )  return ersNOT_FOUND;
        id->_node->_name = name;
        return 0;
    }

    uint get_node_flags( LNID n ) const
    {
        REF id;
        GUARDME;

        if( !_tree.get_node(n, id) )  return 0;
        return  id->_node->_flags;
    }

    bool check_node_autodestroy( LNID n, uint64 timenow )
    {
        REF id;
        GUARDME;

        if( !_tree.get_node(n, id) )  return false;
        uint64 ttd = id->_node->_timetodie;
        if( ttd == 0  ||  ttd > timenow )  return false;
        return true;
    }

    

    opcd service_find( const token& name, const version& svcver, NODEREF<ServiceCoid>& res ) const
    {
        GUARDME;
        ServiceClass svc = _svcreg.find_best( name, svcver );
        if(svc.is_set())
            return get_node( svc->_id, res );

        return ersNO_MATCH;
    }

    opcd service_find_exact( const token& name, const version& svcver, NODEREF<ServiceCoid>& res ) const
    {
        GUARDME;
        ServiceClass svc = _svcreg.find_exact( name, svcver );
        if(svc.is_set())
            return get_node( svc->_id, res );

        return ersNO_MATCH;
    }

    opcd service_find_all( const token& name, dynarray<uint>& lst ) const
    {
        GUARDME;
        dynarray<ServiceClass> slst;
        _svcreg.find_all( name, slst );
        if( slst.size() == 0 )
            return ersNO_MATCH;

        lst.need_new( slst.size() );
        for( uint i=0; i<slst.size(); ++i )
            lst[i] = slst[i]->_id;

        return 0;
    }

    opcd serviceinst_find_best( const token& name, const version& svcver, NODEREF<ServiceInstanceCoid>& res ) const
    {
        GUARDME;
        
        ServiceInstanceClass svci = _svcireg.find_best( name, svcver );
        if(svci.is_set())
            return get_node( svci->_id, res );

        return ersNO_MATCH;
    }

    opcd serviceinst_find( uint soid, const token& name, const version& svcver, NODEREF<ServiceInstanceCoid>& res ) const
    {
        GUARDME;

        if( soid == 0 )
        {
            //connect auto
            ServiceInstanceClass svci = _svcireg.find_best( name, svcver );
            if( svci.is_set() )
                return get_node( svci->_id, res );
            else
                return ersFE_NO_SUCH_SERVICE;
        }

        opcd e = get_node_of_class( soid, "", 0, res );
		if( !e && !name.is_empty() && res->_desc->_name != name && svcver.can_use_provider(res->_desc->_version) )
		{
			res.unset();
			return ersMISMATCHED "name";
		}

        return e;
    }

    opcd serviceinst_find_all( const token& name, dynarray<uint>& lst ) const
    {
        GUARDME;
        dynarray<ServiceInstanceClass> slst;
        _svcireg.find_all( name, slst );
        if( slst.size() == 0 )
            return ersNO_MATCH;

        lst.need_new( slst.size() );
        for( uint i=0; i<slst.size(); ++i )
            lst[i] = slst[i]->_id;

        return 0;
    }



    opcd get_tree_info( dynarray<tree_info>& out, uints* ref ) const;
    opcd get_tree_info_changes( uints& ref, dynarray<tree_info>& out ) const;

/*
    opcd threads_free( uint obj )
    {
        dynarray<uint> tho, thu;
        
        {
            GUARDME;
            REF ref;
            if( _tree.get_node( obj, ref ) )
            {
                tho = ref->_node->_thr_own;
                thu = ref->_node=>_thr_using;
            }
            else
                return ersNOT_FOUND;
        }

        pthread_t self = pthread_self();

        {for( uint i=0; i<tho.size(); ++i )
        {
            if( tho[i] != UMAX32  &&  !pthread_equal( (pthread_t)tho[i], self ) )
                thread_free( tho[i] );
        }}

        {for( uint i=0; i<thu.size(); )
        {
            if( thu[i] != UMAX32  &&  !pthread_equal( (pthread_t)thu[i], self ) )
                thread_free( thu[i] );
        }}

        return 0;        
    }
*/
    opcd thread_unregister( uint obj, uint tid )
    {
        GUARDME;

        REF ref;
        if( _tree.get_node( obj, ref ) )
        {
            ints p = ref->_node->thread_unregister(tid);
            if( p>=0 )
            {
                uint* pc = _change_list.add(2);
                pc[0] = tree_info::CHANGE_DEL_THREAD | obj;
                pc[1] = tid;
                return 0;
            }
            return ersINVALID_PARAMS;
        }
        else
            return ersNOT_FOUND;
    }



    opcd find_nodes_of_class( uint parid, const token& classname, const version* ver, const token& name, dynarray<uint>& lst ) const
    {
        REF idp, id;
        GUARDME;

        if( !_tree.get_node(parid, idp) )
            return ersINVALID_PARAMS;
        id = idp;

        for( ; id.scanto_next_of_class( idp, classname, ver ); )
        {
            if( name.is_empty()  ||  id->_node->_name == name )
                *lst.add(1) = (LNID)id;
        }

        return 0;
    }

    opcd find_nodes_of_class( uint parid, const NodeClass nc, const token& name, dynarray<uint>& lst ) const
    {
        REF idp, id;
        GUARDME;

        if( !_tree.get_node(parid, idp) )  return ersINVALID_PARAMS;
        id = idp;

        for( ; id.scanto_next_of_class( idp, nc ); )
        {
            //if( !id->_node->_node->get_version().can_use_provider(ver) )
            //    continue;
            if( name.is_empty()  ||  id->_node->_name == name )
                *lst.add(1) = (LNID)id;
        }

        return 0;
    }

    template<class T>
    opcd get_superior( uint id, NODEREF<T>& sup ) const
    {
        GUARDME;
        REF rsup;
        if( _tree.get_superior( id, rsup )  &&  rsup->_class.is_of_type( T::GET_INTERFACE() ) )
        {
            if( !rsup->_node->can_attach() )  return ersDENIED;
            sup = rsup->_node;
            return 0;
        }

        return ersFAILED;
    }

    opcd get_superior( uint id, uint* sup ) const
    {
        GUARDME;
        COID csup;
        if( _tree.get_superior( id, &csup ) )
        {
            *sup = (LNID)csup;
            return 0;
        }

        return ersFAILED;
    }

    opcd get_children( uint id, dynarray<uint>& chld ) const
    {
        REF r;
        GUARDME;

        if( !_tree.get_node( id, r ) )  return ersOUT_OF_RANGE;

        if (!r.goto_first())  return 0;
        for(;;)
        {   
            *chld.add(1) = (LNID)r;
            if (!r.goto_next())  break;
        }
        return 0;
    }

    opcd goto_first( uint id, uint* dst ) const
    {
        REF r;
        GUARDME;

        if( !_tree.get_node( id, r ) )  return ersOUT_OF_RANGE;

        if( r.goto_first() )
        {
            *dst = (LNID)r;
            return 0;
        }

        return ersUNAVAILABLE;
    }

    opcd goto_next( uint id, uint* dst ) const
    {
        REF r;
        GUARDME;

        if( !_tree.get_node( id, r ) )  return ersOUT_OF_RANGE;

        if( r.goto_next() )
        {
            *dst = (LNID)r;
            return 0;
        }

        return ersUNAVAILABLE;
    }

    opcd goto_node_of_class( uint id, const token& classname, const version* ver, uint* dst ) const
    {
        REF r, root;
        GUARDME;

        if( !_tree.get_node( id, r ) )  return ersOUT_OF_RANGE;

        if( !r.goto_first_of_class( classname, ver ) )  return ersNOT_FOUND;
        *dst = (LNID)r;
        return 0;
    }

    opcd scanto_node_of_class( uint id, uint parid, const token& classname, const version* ver, uint* dst ) const
    {
        REF r, root;
        GUARDME;

        if( !_tree.get_node( id, r ) )  return ersOUT_OF_RANGE;
        _tree.get_node( parid, root );

        if( r.scanto_next_of_class( root, classname, ver ) )
        {
            *dst = (LNID)r;
            return 0;
        }
        return ersNOT_FOUND;
    }

	bool is_valid( LNID id ) const {return _tree.is_valid( id );}

private:

};





} //namespace coid


#endif //__COID_SERVER_TREEMGR__HEADER_FILE__
